package org.mx.dal.service.impl;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.mx.StringUtils;
import org.mx.dal.entity.Base;
import org.mx.dal.error.UserInterfaceDalErrorException;
import org.mx.dal.service.JdbcBatchAccessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ReflectionUtils;

/**
 * 描述： 基于Spring JdbcTemplate的批量数据处理实现类
 *
 * @author john peng Date time 2019/5/25 7:41 PM
 */
public abstract class AbstractJdbcBatchAccessorImpl implements JdbcBatchAccessor {
	private static final Logger logger = LoggerFactory.getLogger(AbstractJdbcBatchAccessorImpl.class);

	private JdbcTemplate jdbcTemplate;

	/**
	 * 构造函数
	 * 
	 * @param jdbcTemplate JdbcTemplate
	 */
	public AbstractJdbcBatchAccessorImpl(JdbcTemplate jdbcTemplate) {
		super();
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 * 根据定义的Entity类获取对应的表名
	 * @param entityClass Entity类
	 * @return 表名
	 */
	protected abstract String getTableName(Class<?> entityClass);

	/**
	 * 根据定义的Entity类和属性名，获取对应的列名
	 * @param entityClass Entity类
	 * @param fieldName 属性名
	 * @return 列名
	 */
	protected abstract String getColumnName(Class<?> entityClass, String fieldName);

	private Object getFieldValue(Object entity, String fieldName) {
		Field field = ReflectionUtils.findField(entity.getClass(), fieldName);
		field.setAccessible(true);
		return ReflectionUtils.getField(field, entity);
	}

	private List<String> prepareFieldName(Class<?> entityClass, List<String> fields) {
		List<String> tarFields = new ArrayList<>();
		fields.forEach(field -> {
			String name = getColumnName(entityClass, field);
			tarFields.add(name);
		});
		return tarFields;
	}

	private <T extends Base> int batchExecute(String sql, List<T> entities, List<String> fields) throws SQLException {
		int total = 0;
		Connection connection = null;
		try {
			connection = jdbcTemplate.getDataSource().getConnection();
			connection.setAutoCommit(false);
			int[] result = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
				@Override
				public void setValues(PreparedStatement ps, int i) throws SQLException {
					T entity = entities.get(i);
					for (int index = 0; index < fields.size(); index++) {
						ps.setObject(index + 1, getFieldValue(entity, fields.get(index)));
					}
				}

				@Override
				public int getBatchSize() {
					return entities.size();
				}
			});
			for (int r : result) {
				total += r;
			}
			// 提交事务
			connection.commit();
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("Commit the transaction, total: %d.", total));
			}
		} catch (SQLException | DataAccessException ex) {
			// 发生异常，回滚事务
			if (connection != null) {
				connection.rollback();
				if (logger.isWarnEnabled()) {
					logger.warn(String.format("Rollback the transaction, sql: %s, entities' total: %d.", sql,
							entities.size()));
				}
			}
			throw ex;
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
		return total;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see JdbcBatchAccessor#batchInsert(List, List)
	 */
	@Override
	public <T extends Base> int batchInsert(final List<String> insertFields, List<T> entities) {
		if (insertFields == null || insertFields.isEmpty() || entities == null || entities.isEmpty()) {
			if (logger.isWarnEnabled()) {
				logger.warn("The insert fields or the entities is empty.");
			}
			return 0;
		}
		if (entities.size() > 3000) {
			if (logger.isWarnEnabled()) {
				logger.warn(
						"The entities is bigger than 3000, it would lead to high transaction impact on database performance.");
			}
		}
		// 组装INSERT SQL
		Class<?> entityClass = entities.get(0).getClass();
		String table = getTableName(entityClass);
		List<String> tarInsertFields = prepareFieldName(entityClass, insertFields);
		String fields = StringUtils.merge(tarInsertFields, ",");
		String values = StringUtils.repeat(tarInsertFields.size() * 2 - 1, "?,");
		String insertSql = String.format("INSERT INTO %s(%s) VALUES(%s)", table, fields, values);
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Batch insert: %s.", insertSql));
		}
		try {
			int total = batchExecute(insertSql, entities, insertFields);
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("Batch insert successfully, sql: %s, entities: %d, result: %d.", insertSql,
						entities.size(), total));
			}
			return total;
		} catch (SQLException ex) {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("Batch insert fail, sql: %s, entities: %d.", insertSql, entities.size()),
						ex);
			}
			throw new UserInterfaceDalErrorException(UserInterfaceDalErrorException.DalErrors.DB_BATCH_INSERT_FAIL);
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see JdbcBatchAccessor#batchUpdate(List, List, List)
	 */
	@Override
	public <T extends Base> int batchUpdate(List<String> primaryKeyFields, List<String> updateFields,
			List<T> entities) {
		if (primaryKeyFields == null || primaryKeyFields.isEmpty() || updateFields == null || updateFields.isEmpty()
				|| entities == null || entities.isEmpty()) {
			if (logger.isWarnEnabled()) {
				logger.warn("The primary key fields or the update fields or the entities is empty.");
			}
			return 0;
		}
		if (entities.size() > 3000) {
			if (logger.isWarnEnabled()) {
				logger.warn(
						"The entities is bigger than 3000, it would lead to high transaction impact on database performance.");
			}
		}
		// 组装UPDATE SQL
		Class<?> entityClass = entities.get(0).getClass();
		String table = getTableName(entityClass);
		List<String> tarUpdateFields = prepareFieldName(entityClass, updateFields);
		List<String> tarPrimaryKeyFields = prepareFieldName(entityClass, primaryKeyFields);
		StringBuffer sbFields = new StringBuffer();
		tarUpdateFields.forEach(field -> {
			sbFields.append(field);
			sbFields.append("=?,");
		});
		String fields = sbFields.substring(0, sbFields.length() - 1);
		StringBuffer sbPrimary = new StringBuffer();
		tarPrimaryKeyFields.forEach(key -> {
			sbPrimary.append(key);
			sbPrimary.append("=?,");
		});
		String primary = sbPrimary.substring(0, sbPrimary.length() - 1);
		String updateSql = String.format("UPDATE %s SET %s WHERE %s", table, fields, primary);
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Batch update: %s.", updateSql));
		}
		// 使用原有的字段名获取数据
		List<String> mergFields = new ArrayList<>();
		mergFields.addAll(updateFields);
		mergFields.addAll(primaryKeyFields);
		try {
			int total = batchExecute(updateSql, entities, mergFields);
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("Batch update successfully, sql: %s, entities: %d, total: %d.", updateSql,
						entities.size(), total));
			}
			return total;
		} catch (SQLException ex) {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("Batch update fail, sql: %s, entities: %d.", updateSql, entities.size()),
						ex);
			}
			throw new UserInterfaceDalErrorException(UserInterfaceDalErrorException.DalErrors.DB_BATCH_UPDATE_FAIL);
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see JdbcBatchAccessor#batchDelete(List, List)
	 */
	@Override
	public <T extends Base> int batchDelete(List<String> primaryKeyFields, List<T> entities) {
		if (primaryKeyFields == null || primaryKeyFields.isEmpty() || entities == null || entities.isEmpty()) {
			if (logger.isWarnEnabled()) {
				logger.warn("The primary key fields or the entities is empty.");
			}
			return 0;
		}
		if (entities.size() > 3000) {
			if (logger.isWarnEnabled()) {
				logger.warn(
						"The entities is bigger than 3000, it would lead to high transaction impact on database performance.");
			}
		}
		// 组装DELETE SQL
		Class<?> entityClass = entities.get(0).getClass();
		String table = getTableName(entityClass);
		List<String> tarPrimaryKeyFields = prepareFieldName(entityClass, primaryKeyFields);
		StringBuffer sb = new StringBuffer();
		tarPrimaryKeyFields.forEach(key -> {
			sb.append(key);
			sb.append("=?,");
		});
		String primary = sb.substring(0, sb.length() - 1);
		String deleteSql = String.format("DELETE FROM %s WHERE %s", table, primary);
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Batch delete: %s.", deleteSql));
		}
		try {
			int total = batchExecute(deleteSql, entities, primaryKeyFields);
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("Batch delete successfully, sql: %s, entities: %d, total: %d.", deleteSql,
						entities.size(), total));
			}
			return total;
		} catch (SQLException ex) {
			if (logger.isErrorEnabled()) {
				logger.error(String.format("Batch delete fail, sql: %s, entities: %d.", deleteSql, entities.size()),
						ex);
			}
			throw new UserInterfaceDalErrorException(UserInterfaceDalErrorException.DalErrors.DB_BATCH_DELETE_FAIL);
		}
	}
}
